/* Filename: add.c
 * $ ps axm
 * $ ps ax -L
 * Description: 信号量数组
 * $ gcc -Wall add.c -o add
 * $ echo 1 > /tmp/out
 * $ ./add && cat /tmp/out
 * Last modified: humble 20200502 17:07
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define PROCNUM (20)
#define LINEBUFSIZE (1024)
#define FNAME "/tmp/out"

static int semid;


static void usage(void);

static void P(void)
{
    struct sembuf op; //op[NUM]

    op.sem_num = 0;        /* Operate on semaphore 0 */
    op.sem_op = -1;	       /* Wait for value to equal 0 */
    op.sem_flg = 0;

    while(semop(semid, &op, 1) < 0){ //1是指op数组元素个数
        if(errno != EAGAIN || errno != EINTR){
            perror("semop");
            exit(1);
        }
    }
}

static void V(void)
{
    struct sembuf op; //op[NUM]

    op.sem_num = 0;        /* Operate on semaphore 0 */
    op.sem_op = 1;	       /* Wait for value to equal 0 */
    op.sem_flg = 0;

    while(semop(semid, &op, 1) < 0){ //1是指op数组元素个数
        if(errno != EAGAIN || errno != EINTR){
            perror("semop");
            exit(1);
        }
    }

}

static void proc_add(void)
{
    FILE *fp;
    char linebuf[LINEBUFSIZE];

    fp = fopen(FNAME, "r+");
    if(!fp){
        perror("fopen()");
        exit(1);
    }

    P();
    fgets(linebuf, LINEBUFSIZE, fp);
    fseek(fp, 0, SEEK_SET);
    //sleep(1);
    fprintf(fp, "%d\n", atoi(linebuf) + 1);
    fflush(fp);
    V();

    fclose(fp); //关闭的时候会意外解锁，所以需要在解锁后再close

    return;
}

int main(int argc, char **argv)
{
    int i;
    pid_t pid;

    if(argc != 1){
        fprintf(stderr, "argc !=1");
        usage();
        exit(1);
    }

    //key = ftok(); //因为此处通讯有亲缘关系，所以无需生成key，直接用IPC_PRIVATE

    semid = semget(IPC_PRIVATE, 1, 0600); //数组长度为1
    if(semid < 0){
        perror("semget()");
        exit(1);
    }

    if(semctl(semid, 0, SETVAL, 1) < 0){ //数组第0个元素的总量设为1
        perror("semctl()");
        exit(1);
    }

    for(i = 0; i < PROCNUM; i++)
    {
        pid = fork();
        if(pid < 0){
            perror("fork()");
            //<TODO>需要用一个for来wait，对已经创建的子进程收尸？
            exit(1);
        }

        if(pid == 0){
            proc_add();
            exit(0);
        }
    }

    for(i = 0; i < PROCNUM; i++)
    {
        wait(NULL);
    }

    if(semctl(semid, 0, IPC_RMID) < 0){ //删除semid，此时semnum无效
        perror("semctl()");
        exit(1);
    }
    return 0;
}

static void usage(void)
{
#define USAGE "Usage:\n\
  Helloworld\n"
    printf(USAGE);
}
